#!/usr/bin/python

"""
 Module: showtags.py
 Ver: 2.2; Rev Last 11/02/2012, Previous 10/21/2012

 Rev 2.0; Last: 01/18/2012 - Previous: 12/23/2011 (AEM)
   - Minor bug fixes
   - Move SongTitleLbl to the SongInfoPanel
   - Decrease space between labels and text boxes in SongInfoPanel
   - Change SongTitlePanel to FileListPanel; increase height of panel
   - Add file multi-select file open dialog
   - Add listctrl to SongInfoPanel
   - Add file meta data load to listctrl click event
   - Auto load image and comment field from xml files
   - Add cfg file, cfg file read, set cfg items functions

 Rev 2.1; Last: 08/29/2012 - Previous: 08/28/2012  (AEM)
   - Add runtime logging
   - Remove remaining (almost) debug code
   - Add empty log function
   - Add text control box color coding
   - Add song list refresh function
   - Add file remove/list ctrl row remove function
   - Change artist name ctrl from static text to a text box for
     editing and saving.

 Rev 2.2; Last: 11/02/2012 - Previous: 10/09/2012  (AEM)
   - Add module maingui
   - Add module getstream -- BUT 11/02/2012 removed this module
                             and converted it into a separate program.
   - Make logging initialization runtime aware
   - Added content checking on Release Date value before saving tags
   - Update file name in SongListCtrl when renaming a file

 Rev 2.2.1; Last: 02/12/2013 - Previous: 02/11/2013  (AEM)
   - Clean up to match PEP8 style guide as closely as practical
   - Clean up to match pylint style guide as closely as practical
   - Add save tags button on main gui, functions same as menu save action
   - Refine error checking on config file settings

 This is the main program providing control logic and UI

 - Custom modules needed by this appl
   - gettags.py:       Extract frame data from MP3 files
   - getlastfminfo.py: Request song/album info from last.fm
   - cfginit.py:       Read and return values from cfg file

 Dependencies
   - wx.Python:
       install on Ubuntu/Debian: sudo apt-get install python-wxtools
       install on Fedora: su -c 'yum install wxPython.i386'
   - Mutagen:
       install on Ubuntu/Debian: sudo apt-get install python-mutagen
       install on Fedora: su -c 'yum install python-mutagen'
   - ElementTree:
       if Python ver >= 2.5, should already be installed

 Required local settings:
   - self.AudioDir = 'your/local/default/path'
     Default start audio file dir
     Value is assigned from the config file

 Copyright (C) 2010-2012  Arnold E. Morgan

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 GNU General Public License for more details, http://www.gnu.org/licenses/.

"""

import wx.lib.hyperlink
import wx
#import time
import datetime
import os
import sys
import urllib2
import cStringIO
import logging

# *** Custom Modules ***
import gettags
import getlastfminfo
import cfginit
import maingui


class MssgStatusBar(wx.StatusBar):
   """ Create a 2 cell status bar """
   def __init__(self, parent):
      wx.StatusBar.__init__(self, parent)

      self.SetFieldsCount(2)
      self.SetStatusText('Welcome to ShowTags', 0)
      self.SetStatusWidths([-5, -2])


class MainTagFrame(wx.Frame):
   """ The application main frame.

    Segregated into 5 sections: main menu, file list panel,
    song/album info panel, file info panel, status bar.

    Enables editing song title, album name, record date, release date,
    genre, file name.
   """

   def __init__(self, parent, ID, title, theDebugLevel):
      """ Create, place and initialize all the GUI elements """
      wx.Frame.__init__(self, parent, ID, title, size=(1000, 900))

      # Class level attributes.
      # The multi-song list control. Once populated enables scrolling through
      # a summary list of available audio files. Clicking on a row will
      # populate the Song/Album and File Info text boxes.
      self.SongListCtrl = None

      # This attribute tells the file open dialog where to look for audio
      # files. Gets reset on each file open operation. Default start up value
      # is assigned from the config file.
      self.AudioDir = ''
      self.AudioFileName = ''            # Current active file

      self.FullFilePath = ''
      self.myTrackInfo = ''              # The always on GetLastFM object
                                         # Instantiated in the set_config
                                         # method becasue it needs values
                                         # from the config file

      self.IsLogging = False              # Logging decision set in cfg file
      self.appDebugLevel = theDebugLevel  # Applicaton-wide debug flag
                                          # Set the amount of run time
                                          # debug and info messaging

      self.LastFMok = False               # If API Key and Root URL to Last.FM
                                          # appear ok at config file read time,
                                          # enable appropriate menu options.
                                          # Otherwise, disable Last.FM related
                                          # menus.

      # Set up installation specific config values.
      (rtnstat, Mssg) = self.set_config()
      if not rtnstat:
         logging.error(Mssg)
         wx.MessageBox(Mssg, 'Info', wx.OK | wx.ICON_INFORMATION)
         self.Close(True)
      else:
         logging.debug('Config file values set ok')

      # ------- Menu ---------
      self.create_menu()
      # ---------------------

      # Panel 1 (p1) = Song/Album Info panel
      self.p1titlepos = 25   # *** Panel 1 label text horiz starting position
      self.p1valuepos = 125  # *** Panel 1 value text horiz starting position

      self.p1vpos = 280  # Panel 1 label and value text vert starting position
      self.p1vinc = 25   # Vertical separation increment between labels

      # Panel 2 (p2) = File Info panel
      self.p2titlepos = 25   # *** Panel 2 label text horiz starting position
      self.p2valuepos = 120  # *** Panel 2 value text horiz starting position

      self.p2vpos = 60  # Panel 2 label and value text vert starting position
      self.p2vinc = 20  # Vertical separation increment between labels

      # --------- Initialize StatusBar Text ----------
      self.MainStatusBar = MssgStatusBar(self)
      self.SetStatusBar(self.MainStatusBar)
      CurrentDT = datetime.datetime.now()
      FormattedDT = CurrentDT.strftime("%Y-%m-%d %H:%M:%S")
      self.MainStatusBar.SetStatusText(FormattedDT, 1)
      # ----------------------------------------------

      # ----------- Init main and album picture panels/sizers ---------------
      #maingui.InitSizers(self)
      maingui.init_sizers(self)

      #*** Create the listctrl component that will hold the meta data
      #    for multiple mp3 files
      #self.InitSongListCtrl()
      self.init_song_list_ctrl()
      #mycnt = myself.SongListCtrl.GetColumnCount()

      # ---------------- Song/Album Info Panel ------------------------------
      #maingui.SetSongInfoPanel(self)
      maingui.set_song_info_panel(self)

      # ---------------- File Info Panel ------------------------------------
      #maingui.SetFileInfoPanel(self)
      maingui.set_file_info_panel(self)

      #----------------------------------------------------------------------
      hbox = wx.BoxSizer(wx.HORIZONTAL)
      hbox.Add(self.SongInfoPanel, 4, wx.EXPAND | wx.ALL)
      hbox.Add(self.FileInfoPanel, 3, wx.EXPAND)

      self.vbox0.Add(hbox, 10, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 10)

      self.SetAutoLayout(True)
      self.SetSizer(self.vbox0)
      self.Layout()
      # *** End of MainTagFrame __init__ ***

   def set_config(self):
      """
      Set installation specific config values for module showtags
      and module getlastfminfo.

      Set the XML files location. If not supplied by the cfg file,
      and not a valid directory, set it to "xmlfiles" below the
      install directory.
      Also instantiate the GetLastFM object. Any references to the
      Last FM obj will use the self.myTrackInfo object we create here.
      """
      # Return state: 0 = fail, 1 = ok
      # final state set by get_cfg_settings
      LocalMssg = ""
      RtnStat = 0
      (RtnStat, RtnMssg, InitValues) = cfginit.get_cfg_settings()
      if not RtnStat:
         #*** MSSG LEVEL CRITICAL ***
         #*** Calling fcn will display the mssg ***
         pass
      else:
         if self.appDebugLevel >= 2:
            logging.debug("InitValues dictionary:")
            keylist = InitValues.keys()
            #keylist.sort()
            for keyval in keylist:
               logging.debug("%-25s   %-s" % (keyval, InitValues[keyval]))


         if 'EnableLog' in InitValues:
            if InitValues['EnableLog'] == 'True':
               self.IsLogging = True

         XMLFilePath = os.getcwd() + '/xmlfiles/'
         if 'XMLPath' in InitValues:
            if os.path.exists(InitValues['XMLPath']):
               XMLFilePath = InitValues['XMLPath']
               # Use of xml path is assumed to end with a '/'
               if XMLFilePath[-1:] != '/' and XMLFilePath[-1:] != '\\':
                  XMLFilePath = XMLFilePath + '/'

         # Set location of audio files in self.AudioDir
         # for this installation. If the location does not exist,
         # defaults to install directory.
         if 'DefMusicDir' in InitValues:
            if os.path.exists(InitValues['DefMusicDir']):
               self.AudioDir = InitValues['DefMusicDir']  # Default start dir

         # Make sure *something* is set for the Last.FM API key
         LastFMKey = ""
         LastFMurl = ""
         if 'APIKey' in InitValues:
            if len(InitValues['APIKey']) == 0:
               LocalMssg = "An API key for Last.FM is required to " \
                           "update MP3 files. Please register with Last.FM " \
                           "to obtain the key and enter the value in the " \
                           "showtags.cfg file."
               wx.MessageBox(LocalMssg, 'Info', wx.OK | wx.ICON_INFORMATION)
            else:
               LastFMKey = InitValues['APIKey']

               # Make sure *something* is set for the Last.FM URL
               if 'RootURL' in InitValues:
                  if len(InitValues['RootURL']) == 0:
                     LocalMssg = "The root URL to Last.FM is required to " \
                                 "update MP3 files. Please enter that " \
                                 "value in the showtags.cfg file."
                     wx.MessageBox(LocalMssg, 'Info',
                                   wx.OK | wx.ICON_INFORMATION)
                  else:
                     # Last.FM ok only if both Key and URL are provided.
                     self.LastFMok = True
                     LastFMurl = InitValues['RootURL']

         self.myTrackInfo = getlastfminfo.GetLastFM(self.appDebugLevel,
                                                    LastFMKey, LastFMurl,
                                                    XMLFilePath)
      return (RtnStat, RtnMssg)

   def create_menu(self):
      menubar = wx.MenuBar()
      MP3 = wx.Menu()
      mExit = wx.Menu()
      mGetInfo = wx.Menu()
      AboutMenu = wx.Menu()

      self.mp3OpenMultiMenu = MP3.Append(wx.ID_ANY, "Select File &List",
                                                    " Open new mp3 files")
      #mp3Reload = MP3.Append(wx.ID_ANY, "&Reload Last File")
      self.mp3ReloadMenu = MP3.Append(wx.ID_ANY, "&Reload File List")
      MP3.AppendSeparator()
      self.EmptyLogMenu = MP3.Append(wx.ID_ANY, "&Truncate Log")
      MP3.AppendSeparator()
      self.mp3SaveMenu = MP3.Append(wx.ID_ANY, '&Save Tags')
      self.mp3RenameMenu = MP3.Append(wx.ID_ANY, 'Rename &File')
      #MP3.AppendSeparator()

      self.TrackInfoFromLastFMMenu = mGetInfo.Append(wx.ID_ANY,
                                                     'Track Info from Last FM')
      self.AlbumInfoFromLastFMMenu = mGetInfo.Append(wx.ID_ANY,
                                                     'Album Info from Last FM')
      mGetInfo.AppendSeparator()
      self.TrackInfoFromFileMenu = mGetInfo.Append(wx.ID_ANY,
                                                   'Track Info from XML File')
      self.AlbumInfoFromFileMenu = mGetInfo.Append(wx.ID_ANY,
                                                   'Album Info from XML File')

      ExitItem = mExit.Append(wx.ID_EXIT, "E&xit ShowTags")
      AboutItem = AboutMenu.Append(wx.ID_ANY, "About &ShowTags")

      self.Bind(wx.EVT_MENU, self.on_open_multi, self.mp3OpenMultiMenu)
      self.Bind(wx.EVT_MENU, self.on_exit, ExitItem)
      self.Bind(wx.EVT_MENU, self.get_track_last_fm,
                self.TrackInfoFromLastFMMenu)

      self.Bind(wx.EVT_MENU, self.get_track_file, self.TrackInfoFromFileMenu)

      self.Bind(wx.EVT_MENU, self.get_album_last_fm,
                self.AlbumInfoFromLastFMMenu)

      self.Bind(wx.EVT_MENU, self.get_album_file, self.AlbumInfoFromFileMenu)

      #self.Bind(wx.EVT_MENU, self.ReloadLastFile, mp3Reload)
      self.Bind(wx.EVT_MENU, self.reload_file_list, self.mp3ReloadMenu)

      self.Bind(wx.EVT_MENU, self.truncate_log_file, self.EmptyLogMenu)
      self.Bind(wx.EVT_MENU, self.on_save, self.mp3SaveMenu)
      self.Bind(wx.EVT_MENU, self.on_rename, self.mp3RenameMenu)
      self.Bind(wx.EVT_MENU, self.show_about, AboutItem)

      # Logging true/false is set in the cfg file
      self.EmptyLogMenu.Enable(self.IsLogging)

      menubar.Append(MP3, '&MP3')
      menubar.Append(mGetInfo, 'Get Info')
      menubar.Append(mExit, 'E&xit')
      menubar.Append(AboutMenu, '&About')
      self.SetMenuBar(menubar)

      # Finally, if critical Last.FM options not provided,
      # disable those menu options.
      if not self.LastFMok:
         self.set_last_fm_state(False)

   def show_about(self, event):
      """ Show the about dialog box """
      cfginit.OnAboutBox()

   def set_last_fm_state(self, LastFMState):
      """ If Last.FM access not allowed, disable the
      appropriate menu options.
      """

      self.TrackInfoFromLastFMMenu.Enable(LastFMState)
      self.AlbumInfoFromLastFMMenu.Enable(LastFMState)

   def init_song_list_ctrl(self):
      self.SongListCtrl = wx.ListCtrl(self.FileListPanel, -1, pos=(3, 1),
                                      size=(970, 265),
                                      style=wx.LC_REPORT | wx.BORDER_SUNKEN |
                                      wx.LC_HRULES)

      # List of tuple column assignments
      ColData = [(0, 150, "Artist"), (1, 170, "Song Title"),
                 (2, 180, "Album Name"), (3, 80, "Year"), (4, 100, "Genre"),
                 (5, 80, "File Date"), (6, 200, "File Name")]

      self.SongListCtrl.Bind(wx.EVT_LIST_ITEM_SELECTED,
                             self.on_item_selected)
      self.SongListCtrl.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK,
                             self.on_list_right_click)

      self.SongListCtrl.SetFont(wx.Font(9, wx.SWISS, wx.NORMAL, wx.NORMAL))

      for (ColIndex, ColWidth, ColTitle) in ColData:
         self.SongListCtrl.InsertColumn(ColIndex, ColTitle)
         self.SongListCtrl.SetColumnWidth(ColIndex, ColWidth)

   def on_list_right_click(self, event):
      """ Remove both the list ctrl row and the mp3 file it represents. """

      RightClickIndex = event.GetIndex()
      #RightClickIndex = event.m_itemIndex   # Either one of these will work
      CurrentItem = self.SongListCtrl.GetItem(RightClickIndex, 6)
      FileName = CurrentItem.GetText()

      MssgAns = wx.MessageBox("Delete the mp3 file at index %d?\n%s" %
                              (RightClickIndex, FileName), 'Confirm',
                              wx.YES_NO | wx.ICON_QUESTION)

      if MssgAns == wx.YES:
         os.remove(self.AudioDir + "/" + FileName)
         self.SongListCtrl.DeleteItem(RightClickIndex)
      else:
         #wx.MessageBox("Delete cancelled",
         #               'Info', wx.OK | wx.ICON_INFORMATION)
         pass

   def on_item_selected(self, event):
      """ Event initiated when a row in the list control was clicked. """

      currentIndex = event.m_itemIndex
      currentItem = self.SongListCtrl.GetItem(currentIndex, 6)
      FileName = currentItem.GetText()

      self.AudioFileName = FileName
      self.clear_previous_img()

      self.DisplayFileMeta()

      currentItem = self.SongListCtrl.GetItem(currentIndex, 0)
      ArtistName = currentItem.GetText()

      currentItem = self.SongListCtrl.GetItem(currentIndex, 1)
      SongTitle = currentItem.GetText()

      currentItem = self.SongListCtrl.GetItem(currentIndex, 2)
      AlbumTitle = currentItem.GetText()

      #PopMssg = "Item Selected: " + str(currentIndex) + \
      #    "\nArtist Name: " + ArtistName + "\nSong Title: " + SongTitle + \
      #    "\nAlbum Title: " + AlbumTitle + "\nFile Name: " + FileName

      #PopMssg = "Item Selected: " + str(currentIndex) + \
      #    "\nFile Name: " + FileName

      #wx.MessageBox(PopMssg, 'Info', wx.OK | wx.ICON_INFORMATION)

      #*** If an xml file exists for this song, pull out the image location
      #    and comments
      #*** Functions like get_track_file() ***
      self.get_special_items(ArtistName, SongTitle, AlbumTitle)

   #************************** ******* *************************************
   def on_exit(self, e):
      self.Close(True)

   #************************** Rev 1.1 *************************************
   def on_open_multi(self, e):
      """
      Open multiple files to populate the list control.
      The first file and path in the list will be passed to the UI populate
      function.
      The assumption is that files will be chosen from only one directory.
      """

      TempDirName = ""
      FileNameList = []
      dlg = wx.FileDialog(self, "Choose a set of files", self.AudioDir,
                          "", "*.mp3",
                          wx.OPEN | wx.MULTIPLE)

      if dlg.ShowModal() == wx.ID_OK:
         FileNameList = dlg.GetFilenames()
         TempDirName = dlg.GetDirectory()
         self.AudioDir = TempDirName
      dlg.Destroy()

      if len(FileNameList) > 0 and len(TempDirName) > 0:
         self.clear_previous_img()
         self.set_song_list_content(FileNameList)
         # self.AudioFileName is set in set_song_list_content
         logging.debug("Audio Dir: **%s** Audio File Name: **%s**" %
                       (self.AudioDir, self.AudioFileName))

         self.DisplayFileMeta()
      else:
         self.MainStatusBar.SetStatusText("File open cancelled or "
                                          "no file selected", 0)

   def clear_previous_img(self):
      """ Clear the previous image and comment text, ready for new image. """
      self.Albumbmp = wx.EmptyBitmap(190, 190)
      dc = wx.MemoryDC()
      dc.SelectObject(self.Albumbmp)
      dc.SetBackground(wx.Brush("#F0F0F0"))
      dc.Clear()
      del dc

      wx.StaticBitmap(self.PicturePanel, wx.ID_ANY, self.Albumbmp, pos=(5, 5))
      self.PicturePanel.Refresh()
      self.set_comment_link("", "")

   def DisplayFileMeta(self):
      self.FullFilePath = os.path.join(self.AudioDir, self.AudioFileName)
      self.MainStatusBar.SetStatusText(self.FullFilePath, 0)

      showdata = TagData(self, self.myTrackInfo, self.appDebugLevel)
      showdata.populate_values(self.FullFilePath)

   #************************************************************************
   def set_song_list_content(self, FileList):
      """
          Populate the list control with values retrieved from the multiple
          audio files selected from disk.
      """

      RedColor = wx.Colour(219, 167, 182)
      IndexCnt = 0
      if self.SongListCtrl.GetItemCount() > 0:
         #self.SongListCtrl.ClearAll() don't do this!
         self.SongListCtrl.DeleteAllItems()

      for NewFileName in FileList:
         TempPath = self.AudioDir + "/" + NewFileName
         myMP3 = gettags.get_mp3_stream_tags(TempPath)
         myID3 = gettags.get_mp3_name_tags(TempPath)

         TempFileDate = str(myMP3['created'])
         self.SongListCtrl.InsertStringItem(IndexCnt, str(myID3['artist']))
         self.SongListCtrl.SetStringItem(IndexCnt, 1, str(myID3['songtitle']))
         self.SongListCtrl.SetStringItem(IndexCnt, 2, str(myID3['albumname']))
         self.SongListCtrl.SetStringItem(IndexCnt, 3, str(myID3['year']))
         self.SongListCtrl.SetStringItem(IndexCnt, 4,
                                         str(myID3['contenttype']))
         self.SongListCtrl.SetStringItem(IndexCnt, 5, TempFileDate[:10])

         # Warn about extended unicode charaters
         NewerName = NewFileName.encode('ascii', 'replace')
         QFind = NewerName.find('?')

         self.SongListCtrl.SetStringItem(IndexCnt, 6, NewFileName)

         if IndexCnt % 2:
            color = wx.Colour(255, 255, 255)
         else:
            color = wx.Colour(237, 243, 254)

         # If any extended chars in file, warn as a red bg row
         if QFind > -1:
            self.SongListCtrl.SetItemBackgroundColour(IndexCnt, RedColor)
         else:
            self.SongListCtrl.SetItemBackgroundColour(IndexCnt, color)

         if IndexCnt == 0:
            self.AudioFileName = NewFileName
         IndexCnt = IndexCnt + 1

      if IndexCnt > 0:
         # Show number of files in the list
         ColTitle = self.SongListCtrl.GetColumn(6)
         ColTitle.SetText("File Name (" + str(IndexCnt) + ")")
         self.SongListCtrl.SetColumn(6, ColTitle)

   #************************************************************************
   def on_save(self, e):
      """ Update the tag data in the MP3 file.

      If the edit value check box is not checked, pass an empty string
      to the save method; only len(val) > 0 will be saved.
      """

      # But first, check format of Release Date field.
      theMssg = ""
      if self.RelDateCB.IsChecked():
         RelDateVal = self.RelDateBox.GetValue()
         DateValLen = len(RelDateVal)

         if DateValLen == 0 or DateValLen > 10:
            theMssg = 'Warning: Length problem with this value:\n"' + \
                      RelDateVal + \
                      '"\nRelease Date tag is expecting a date value with ' \
                      'a format ' + \
                      "of YYYY-MM-DD.\nPlease correct the release date " \
                      "value before saving."
         elif DateValLen == 10:
            theYear = RelDateVal[0:4]
            theMon = RelDateVal[5:7]
            theDay = RelDateVal[8:]
            if (not theYear.isdigit() or not theMon.isdigit() or not theDay.isdigit()):
               theMssg = 'Warning: Format problem with this ' \
                         'value: \n"' + RelDateVal + \
                         '"\nRelease Date tag is expecting a date value ' \
                         'with a format ' + \
                         "of YYYY-MM-DD.\nPlease correct the release " \
                         "date value before saving."

         if len(theMssg) > 0:
            MssgAns = wx.MessageBox(theMssg, 'Confirm', wx.OK | wx.ICON_ERROR)
            return

      NewTitleVal = ""
      NewAlbumVal = ""
      NewArtistVal = ""
      NewRecDateVal = ""
      NewRelDateVal = ""
      NewContentTypeVal = ""

      if self.NewTitleCB.IsChecked():
         NewTitleVal = self.NewTitleBox.GetValue()

      if self.AlbumNameCB.IsChecked():
         NewAlbumVal = self.AlbumNameBox.GetValue()

      if self.ArtistNameCB.IsChecked():
         NewArtistVal = self.ArtistNameBox.GetValue()

      if self.RecDateCB.IsChecked():
         NewRecDateVal = self.RecDateBox.GetValue()

      if self.RelDateCB.IsChecked():
         NewRelDateVal = self.RelDateBox.GetValue()

      if self.ContentTypeCB.IsChecked():
         NewContentTypeVal = self.ContentTypeBox.GetValue()

      #print "New Title: %s, New Album: %s, New Rel Date: %s,
      #     New Artist: %s New Genre: %s" % \
      #(NewTitleVal, NewAlbumVal, NewRelDateVal, NewArtistVal,
      # NewContentTypeVal)

      showdata = TagData(self, self.myTrackInfo, self.appDebugLevel)
      showdata.save_tag_values(self.FullFilePath, NewTitleVal, NewAlbumVal,
                               NewArtistVal, NewRecDateVal, NewRelDateVal,
                               NewContentTypeVal)

      # Design note: if save state was ok, update the song list ctrl also

   def on_rename(self, e):
      """
          Rename the MP3 file; the location is unchanged.
          Also update the name in the SongListCtrl.
      """

      # Replace the new file name in SongListCtrl col 6 for the selected row.
      currentIndex = self.SongListCtrl.GetFirstSelected()

      ErrFlag = False
      FailMssg = "Rename OK"
      if self.appDebugLevel >= 1:
         logging.info("Current file path: %s" % self.FullFilePath)

      (HeadPath, TailName) = os.path.split(self.FullFilePath)
      if self.appDebugLevel >= 1:
         logging.info("Head and tail: ---%s--- ---%s---" %
                      (HeadPath, TailName))

      NewFileName = self.FNameBox.GetValue()
      NewPathAndName = HeadPath + "/" + NewFileName
      if self.appDebugLevel >= 1:
         logging.info("New path and name: %s" % NewPathAndName)

      try:
         os.rename(self.FullFilePath, NewPathAndName)
      except OSError, mssg:
         FailMssg = "Cannot rename file %s; reason: %s" % \
                    (NewPathAndName, mssg)
         ErrFlag = True

      #self.SetInfoMssg(FailMssg)
      self.set_info_mssg(FailMssg)
      if not ErrFlag:
         self.SongListCtrl.SetStringItem(currentIndex, 6, NewFileName)
         self.FullFilePath = NewPathAndName
         self.MainStatusBar.SetStatusText(self.FullFilePath, 0)

   def truncate_log_file(self, e):
      MssgAns = wx.MessageBox("Empty the log file?", 'Confirm',
                              wx.YES_NO | wx.ICON_QUESTION)

      if MssgAns == wx.YES:
         if os.path.exists('showmssg.log'):
            try:
               fo = open('showmssg.log', 'w')
               fo.close()
            except IOError as e:
               Mssg = "I/O error({0}): {1}".format(e.errno, e.strerror)
               wx.MessageBox(Mssg, 'Info', wx.OK | wx.ICON_ERROR)
               logging.error(Mssg)
            except:
               Mssg = "Unexpected error: %s" % sys.exc_info()[0]
               wx.MessageBox(Mssg, 'Info', wx.OK | wx.ICON_ERROR)
               logging.error(Mssg)

   def ReloadLastFile(self, e):
      """
          Reload the last file from this session.
          This function is no longer used
      """

      showdata = TagData(self, self.myTrackInfo, self.appDebugLevel)
      showdata.populate_values(self.FullFilePath)

      self.MainStatusBar.SetStatusText(self.FullFilePath, 0)

   def reload_file_list(self, e):
      """
          Reload the file list ctrl from this session.
          Loop thru the list ctrl to retrieve the file names as a list object.
          The set_song_list_content function knows what to do with the
          file list.
      """

      SongListCnt = self.SongListCtrl.GetItemCount()
      FileListStr = ""
      FileListNames = []
      for row in range(SongListCnt):
         OneFile = self.SongListCtrl.GetItem(row, 6).GetText()
         FileListStr += OneFile + "\n"
         FileListNames.append(OneFile)

      if SongListCnt > 0:
         self.set_song_list_content(FileListNames)

   def get_track_last_fm(self, e):
      """
      Request track data from last.fm for this artist name and track name
      """

      showdata = TagData(self, self.myTrackInfo, self.appDebugLevel)
      showdata.populate_last_fm_track_values(self.ArtistNameBox.GetValue(),
                                             self.NewTitleBox.GetValue())

   def get_track_file(self, e):
      """
      If a prior track request from last.fm was made, there will be
      be an xml file with that data in it. The called method will signal
      whether the file was located or not.
      """

      showdata = TagData(self, self.myTrackInfo, self.appDebugLevel)
      showdata.populate_file_track_values(self.ArtistNameBox.GetValue(),
                                          self.NewTitleBox.GetValue())

   def get_album_last_fm(self, e):
      """
      Request album data from last.fm for this artist name and track name
      """

      showdata = TagData(self, self.myTrackInfo, self.appDebugLevel)
      showdata.populate_last_fm_album_values(self.ArtistNameBox.GetValue(),
                                             self.AlbumNameBox.GetValue())

   def get_album_file(self, e):
      """
      If a prior album request from last.fm was made, there will be
      be an xml file with that data in it. The called method will signal
      whether the the file was located or not.
      """

      showdata = TagData(self, self.myTrackInfo, self.appDebugLevel)
      showdata.populate_file_album_values(self.ArtistNameBox.GetValue(),
                                          self.AlbumNameBox.GetValue())

   #def SetInfoMssg(self, MssgType = "warn", theMssgType = ""): *** Future ***
   def set_info_mssg(self, theMssgText="Ok"):
      """
          Interface for placing messages in the general purpose
          status text ctrl.
      """

      self.GetInfoStatusDesc.SetLabel(theMssgText)
      self.GetInfoStatusDesc.Wrap(350)
      self.GetInfoStatusDesc.SetForegroundColour('red')

      #*** Future ***
      #if MssgType == "error":
         #self.GetInfoStatusDesc.SetForegroundColour('red')
      #elif MssgType == "info":
         #self.GetInfoStatusDesc.GetForegroundColour('black')
      #else
         #self.GetInfoStatusDesc.GetForegroundColour('red')

   def set_comment_link(self, LinkText, LabelText):
      """
      The comment value will *usually* be a link back to last.fm.
      """

      if LinkText.startswith("http:") or LinkText == "":
         self.CommentDesc.SetLabel("")
         self.CommentLink.SetURL(URL=LinkText)
         self.CommentLink.SetLabel(label=LabelText)
      else:
         self.CommentDesc.SetLabel(LinkText)

   def get_special_items(self, ArtistName, SongTitle, AlbumTitle):
      """
      *** like get_track_file() ***
      """

      #wx.MessageBox("Special Items: " + ArtistName + "--" + SongTitle,
      #       'Info', wx.OK | wx.ICON_INFORMATION)

      MySpecialInfo = TagData(self, self.myTrackInfo, self.appDebugLevel)

      #*** Functions like populate_file_track_values() ***
      MySpecialInfo.populate_special_file_values(ArtistName,
                                                 SongTitle, AlbumTitle)


class TagData():
   """
   Initiate MP3 file data extracts of frame data;
   initiate last.fm or local file song/album requests;
   assign returned values to the UI.
   """

   def __init__(self, parent, lastfmobj, theDebugLevel):
      self.gui = parent
      self.AlbumCoverImage = 0
      self.lastfminfo = lastfmobj
      self.TagDataDebugLevel = theDebugLevel
      self.TagActionState = ""

   def save_tag_values(self, filepath, NewTitleVal, NewAlbumVal, NewArtistVal,
                       NewRecDateVal, NewRelDateVal,
                       NewContentTypeVal):

      """ Save updates to the MP3 file """
      self.TagActionState = "OK"

      self.TagActionState = gettags.save_my_tags(filepath, NewTitleVal,
                                                 NewAlbumVal, NewArtistVal,
                                                 NewRecDateVal, NewRelDateVal,
                                                 NewContentTypeVal)

      self.update_text_ctrl(NewTitleVal, NewAlbumVal, NewArtistVal,
                            NewRecDateVal, NewRelDateVal, NewContentTypeVal)

      # Design note: if save state was ok, update the song list ctrl also
      # But let the caller of this function take care of it
      #self.gui.SetInfoMssg(SaveState)
      #self.gui.SetInfoMssg(self.TagActionState)
      self.gui.set_info_mssg(self.TagActionState)

   def update_text_ctrl(self, SongTitle, AlbumTitle, ArtistName, RecDate,
                        RelDate, ContentType):
      """ Update text ctrl bg color to signify they contain MP3
          file values """

      if len(AlbumTitle) > 0:
         self.gui.AlbumNameBox.SetBackgroundColour((127, 255, 212))

      if len(ArtistName) > 0:
         self.gui.ArtistNameBox.SetBackgroundColour((127, 255, 212))

      if len(SongTitle) > 0:
         self.gui.NewTitleBox.SetBackgroundColour((127, 255, 212))

      if len(RecDate) > 0:
         self.gui.RecDateBox.SetBackgroundColour((127, 255, 212))

      if len(RelDate) > 0:
         self.gui.RelDateBox.SetBackgroundColour((127, 255, 212))

      if len(ContentType) > 0:
         self.gui.ContentTypeBox.SetBackgroundColour((127, 255, 212))

   def populate_values(self, filepath):
      """
          Assign values from the MP3 file to the UI elements.
          Also set the text ctrl bg color to signify mp3 file data.
      """

      myMP3 = gettags.get_mp3_stream_tags(filepath)

      if self.TagDataDebugLevel >= 2:
         for mp3tag in myMP3.keys():
            logging.debug(mp3tag + '\t' + str(myMP3[mp3tag]))

      self.gui.FNameBox.SetValue(myMP3['filename'])
      self.gui.FSizeNum.SetLabel(myMP3['filesize'])
      self.gui.FModDate.SetLabel(myMP3['lastmod'])

      self.gui.FAccDate.SetLabel(myMP3['lastaccess'])
      self.gui.FCreateDate.SetLabel(myMP3['created'])
      self.gui.LayerDesc.SetLabel(str(myMP3['layer']))

      self.gui.BitRateNum.SetLabel(myMP3['bitrate'])
      self.gui.FreqNum.SetLabel(myMP3['samplerate'])
      self.gui.TrackLenNum.SetLabel(str(myMP3['length']))

      self.gui.ModeNum.SetLabel(str(myMP3['mode']))
      self.gui.PadDesc.SetLabel(str(myMP3['padding']))
      self.gui.ProtectDesc.SetLabel(str(myMP3['protected']))

      self.gui.SketchyDesc.SetLabel(str(myMP3['sketchy']))
      self.gui.VerNum.SetLabel(str(myMP3['version']))
      # *****************************************

      myID3 = gettags.get_mp3_name_tags(filepath)

      if self.TagDataDebugLevel >= 2:
         for id3tag in myID3.keys():
            logging.debug(id3tag + '\t' + str(myID3[id3tag]))

      self.gui.AlbumImg.SetLabel("")

      self.gui.SongTitleVal.SetLabel(str(myID3['songtitle']))
      self.gui.SongTitleVal.Wrap(300)

      self.gui.NewTitleBox.SetValue(str(myID3['songtitle']))
      self.gui.NewTitleBox.SetBackgroundColour((127, 255, 212))

      self.gui.SongArtistVal.SetLabel(str(myID3['artist']))
      #self.gui.ArtistName.SetLabel(str(myID3['artist']))
      self.gui.ArtistNameBox.SetValue(str(myID3['artist']))
      self.gui.ArtistNameBox.SetBackgroundColour((127, 255, 212))

      self.gui.TrackNum.SetLabel(str(myID3['track']))
      self.gui.AlbumNameBox.SetValue(str(myID3['albumname']))
      self.gui.AlbumNameBox.SetBackgroundColour((127, 255, 212))

      self.gui.RecDateBox.SetValue(str(myID3['year']))
      self.gui.RecDateBox.SetBackgroundColour((127, 255, 212))

      #self.gui.RadioStDesc.SetLabel(str(myID3['radiostation']))
      self.gui.ContentTypeBox.SetValue(str(myID3['contenttype']))
      self.gui.ContentTypeBox.SetBackgroundColour((127, 255, 212))

      self.gui.EncodedByDesc.SetLabel(str(myID3['encodedby']))
      #self.gui.SetCommentLink(str(myID3['comments']), str(myID3['comments']))
      self.gui.set_comment_link(str(myID3['comments']),
                                str(myID3['comments']))

      self.gui.SubTitleName.SetLabel(str(myID3['subtitle']))
      self.gui.SubTitleName.Wrap(400)

      self.gui.RelDateBox.SetValue(str(myID3['releasetime']))
      self.gui.RelDateBox.SetBackgroundColour((127, 255, 212))

   def show_album_image(self, ImageURLPath):
      """ If an album image from last.fm, show it """

      ErrFlag = False

      self.gui.AlbumImg.SetLabel(ImageURLPath)
      self.opener = urllib2.build_opener()
      #*** Design note: either method works; .read() the opener
      #    or .read() StringIO
      #*** 1) http://dwabbott.com/code/index15.html
      #*** 2) other source can't remember
      #self.image_bytes = self.opener.open(myTrackInfo.AlbumImage)

      try:
         self.image_bytes = self.opener.open(ImageURLPath,
                                             timeout=30.0).read()
      except urllib2.URLError, mssg:
         FailMssg = "Cannot retrieve image from\n%s\nReason:\n%s" % \
                    (ImageURLPath, mssg)
         self.gui.set_info_mssg(FailMssg)
         ErrFlag = True
      except:
         FailMssg = "Unexpected retrieve image error from\n%s\n" \
                    "Reason:\n%s:" % \
                    (ImageURLPath, sys.exc_info()[0])
         self.gui.set_info_mssg(FailMssg)
         ErrFlag = True

      if not ErrFlag:
         self.data_stream = cStringIO.StringIO(self.image_bytes)
         #self.data_stream = cStringIO.StringIO(self.image_bytes.read())
         self.Albumbmp = wx.BitmapFromImage(wx.ImageFromStream(self.data_stream))
         self.AlbumCoverImage = wx.StaticBitmap(self.gui.PicturePanel,
                                                wx.ID_ANY,
                                                self.Albumbmp, pos=(5, 5))

   def populate_file_track_values(self, ArtistName, TrackName):
      """ If an xml file with track data exists locally, show the contents """

      logging.info("Populate xml File Track-Artist: %s, Title: %s" %
                   (ArtistName, TrackName))
      self.lastfminfo.get_track_info_from_file(ArtistName, TrackName)

      TempSave = self.TagDataDebugLevel
      self.TagDataDebugLevel = 2
      if self.TagDataDebugLevel >= 2:
         logging.debug("FailFlag: %s, Status: %s, Mssg: %s" %
                       (self.lastfminfo.FailFlag,
                       self.lastfminfo.xmlgetTrackStatus,
                       self.lastfminfo.TrackErrMssg))

      self.TagDataDebugLevel = TempSave
      self.gui.MainStatusBar.SetStatusText("Get File: " +
                                           self.lastfminfo.xmlgetTrackStatus,
                                           1)

      self.gui.set_info_mssg(self.lastfminfo.TrackErrMssg)

      if not self.lastfminfo.FailFlag:
         self.gui.AlbumNameBox.SetValue(self.lastfminfo.AlbumName)
         self.gui.AlbumNameBox.SetBackgroundColour((255, 255, 0))

         self.gui.ContentTypeBox.SetValue(self.lastfminfo.Genre)
         self.gui.ContentTypeBox.SetBackgroundColour((255, 255, 0))

         self.gui.set_comment_link(self.lastfminfo.myComment,
                                   self.lastfminfo.myComment)

         if self.lastfminfo.AlbumImage != "?":
            self.show_album_image(self.lastfminfo.AlbumImage)

   def populate_last_fm_track_values(self, ArtistName, TrackName):
      """ Request and show track related data from last.fm """

      logging.info("Populate Last FM Track--Artist: %s, Title: %s" %
                   (ArtistName, TrackName))

      self.lastfminfo.get_track_info_from_last_fm(ArtistName, TrackName)

      logging.info("FailFlag: %s, Status: %s, Mssg: %s" %
                   (self.lastfminfo.FailFlag,
                   self.lastfminfo.xmlgetTrackStatus,
                   self.lastfminfo.TrackErrMssg))

      self.gui.MainStatusBar.SetStatusText("Get URL: " +
                                           self.lastfminfo.xmlgetTrackStatus,
                                           1)

      self.gui.set_info_mssg(self.lastfminfo.TrackErrMssg)

      if not self.lastfminfo.FailFlag:
         self.gui.AlbumNameBox.SetValue(self.lastfminfo.AlbumName)
         self.gui.AlbumNameBox.SetBackgroundColour((0, 243, 255))

         self.gui.ContentTypeBox.SetValue(self.lastfminfo.Genre)
         self.gui.ContentTypeBox.SetBackgroundColour((0, 243, 255))

         self.gui.set_comment_link(self.lastfminfo.myComment,
                                   self.lastfminfo.myComment)

         if self.lastfminfo.AlbumImage != "?":
            self.show_album_image(self.lastfminfo.AlbumImage)

   def populate_file_album_values(self, ArtistName, AlbumName):
      """ If an xml file with album data exists locally, show the contents """

      logging.info("Populate xml File Album-Artist: %s, Album: %s" %
                   (ArtistName, AlbumName))

      self.lastfminfo.get_album_info_from_file(ArtistName, AlbumName)
      if self.TagDataDebugLevel >= 1:
         logging.info("Album File FailFlag: %s, Status: %s, Mssg: %s" %
                      (self.lastfminfo.FailFlag,
                      self.lastfminfo.xmlgetAlbumStatus,
                      self.lastfminfo.AlbumErrMssg))

      self.gui.MainStatusBar.SetStatusText("Get File: " +
                                           self.lastfminfo.xmlgetAlbumStatus,
                                           1)

      self.gui.set_info_mssg(self.lastfminfo.AlbumErrMssg)

      if not self.lastfminfo.FailFlag:
         #self.gui.RecTimeBox.SetValue(self.lastfminfo.ReleaseYear.strip())
         self.gui.RelDateBox.SetValue(self.lastfminfo.ReleaseYear.strip())
         self.gui.RelDateBox.SetBackgroundColour((255, 255, 0))

   def populate_last_fm_album_values(self, ArtistName, AlbumName):
      """ Request and show album related data from last.fm """

      logging.info("Populate Last FM Album-Artist: %s, Album: %s" %
                   (ArtistName, AlbumName))

      self.lastfminfo.get_album_info_from_last_fm(ArtistName, AlbumName)

      if self.TagDataDebugLevel >= 1:
         logging.info("Album Last FM FailFlag: %s, Status: %s, Mssg: %s" %
                      (self.lastfminfo.FailFlag,
                      self.lastfminfo.xmlgetAlbumStatus,
                      self.lastfminfo.AlbumErrMssg))

      self.gui.MainStatusBar.SetStatusText("Get URL: " +
                                           self.lastfminfo.xmlgetAlbumStatus,
                                           1)

      self.gui.set_info_mssg(self.lastfminfo.AlbumErrMssg)

      if not self.lastfminfo.FailFlag:
         #self.gui.RecTimeBox.SetValue(self.lastfminfo.ReleaseYear.strip())
         self.gui.RelDateBox.SetValue(self.lastfminfo.ReleaseYear.strip())
         self.gui.RelDateBox.SetBackgroundColour((0, 243, 255))

   def populate_special_file_values(self, ArtistName, TrackName, AlbumTitle):
      """
      If xml files with track and album data exists locally, show the
      album image, comments. (and release date. -- NO)
      Adapted from populate_file_track_values() & populate_file_album_values().
      """

      #wx.MessageBox("populate_special_file_values: " +
      #       ArtistName + "--" + TrackName,
      #       'Info', wx.OK | wx.ICON_INFORMATION)

      self.lastfminfo.get_track_info_from_file(ArtistName, TrackName)

      if self.TagDataDebugLevel >= 1:
         logging.info("Track xml SpecialFileValues-FailFlag: %s, "
                      "Status: %s, Mssg: %s" %
                      (self.lastfminfo.FailFlag,
                      self.lastfminfo.xmlgetTrackStatus,
                      self.lastfminfo.TrackErrMssg))

      if not self.lastfminfo.FailFlag:
         self.gui.set_comment_link(self.lastfminfo.myComment,
                                   self.lastfminfo.myComment)

         if self.lastfminfo.AlbumImage != "?":
            #self.ShowAlbumImage(mySpecialInfo.AlbumImage)
            self.show_album_image(self.lastfminfo.AlbumImage)

      # Don't do this here. This value has already been set in
      # method populate_values.
      #TempAlbumTitle = AlbumTitle.strip()
      #if TempAlbumTitle != "" and TempAlbumTitle != "?" and \
      #   TempAlbumTitle != "unknown":
      #   self.lastfminfo.GetAlbumInfoFromFile(ArtistName, TempAlbumTitle)
      #   self.lastfminfo.get_album_info_from_file(ArtistName, TempAlbumTitle)

      #   if self.TagDataDebugLevel >= 1:
      #      logging.info("Album xml SpecialFileValues-FailFlag: %s, " \
      #                   Status: %s, Mssg: %s" % \
      #          (self.lastfminfo.FailFlag, self.lastfminfo.xmlgetAlbumStatus,
      #           self.lastfminfo.AlbumErrMssg))

      #   if not self.lastfminfo.FailFlag:
      #      self.gui.RelDateBox.SetValue(self.lastfminfo.ReleaseYear.strip())


class ShowTagsMain(wx.App):
   def OnInit(self):
      DebugLevel = 1   # The higher the debug level, the more mssg logging
      self.frame = MainTagFrame(None, wx.ID_ANY, "Show MP3 Tags", DebugLevel)
      self.frame.Centre()
      self.frame.Show()
      return True


def set_log_state():
   """ Find out if the cfg file says to turn on logging.

   If supplied, just return the value from the cfg file
   as a string value.
   The calling fcn will determine the correct action.
   """

   # To support starting showtags.py via a launcher, make sure the
   # location of this module is the current working directory.
   # Otherwise, right off the bat we won't be able to find the
   # config file.
   thisDir = os.path.dirname(os.path.abspath(sys.argv[0]))
   os.chdir(thisDir)

   LogState = "False"
   try:
      for line in open(cfginit.CfgFileName, 'r'):
         TempStr = line.strip()
         if len(TempStr) > 0 and TempStr[0] != "#":
            (theKey, theValue) = TempStr.split("=")
            if theKey == "EnableLog":
               LogState = theValue
               break
   except IOError, (errno, strerror):
      RtnMssg = "InitMain: Config file open error(%s): %s" % (errno, strerror)
      print RtnMssg
   except:
      RtnMssg = "InitMain: Unexpected config file open error: ", \
                sys.exc_info()[0]
      print RtnMssg
   finally:
      #RtnMssg += "\nConfig file settings read complete"
      pass

   #print "*%s*" % LogState
   return LogState


def init_main():
   #print "Log File Exists: %s" % os.path.exists('showmssg.log')
   # First thing to do; establish run time logging settings.
   if os.path.exists('showmssg.log'):
      DateStampNow = datetime.datetime.now()
      NewName = 'showmssg_' + DateStampNow.strftime("%Y%m%d_%H%M") + '.log'
      os.rename('showmssg.log', NewName)

   if set_log_state() == "True":
      # Turn on logging
      ShowLogFormat = '%(levelname)-8s: %(asctime)-15s: %(module)-15s: '
      ShowLogFormat += '%(funcName)-15s: %(lineno)5d: %(message)s'
      logging.basicConfig(filename='showmssg.log', format=ShowLogFormat,
                          level=logging.DEBUG)
      logging.info('logging is turned on...')


if __name__ == "__main__":
   init_main()
   app = ShowTagsMain(0)
   app.MainLoop()
